/*
 * Galaxium Messenger
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Net;

using Anculus.Core;

using Galaxium.Core;

namespace Galaxium.Protocol.Msn
{
	// Thanks to http://miranda.svn.sourceforge.net/viewvc/miranda/trunk/miranda/protocols/MSN/msn_natdetect.cpp?view=markup
	
	public class MsnEchoServer
	{
		IPAddress _address;
		DirectUDPConnection _connection;
		byte[] _buffer = new byte [0];
		
		struct ProbePacket
		{
			public byte Version;
			public byte ServiceCode;
			public ushort ClientPort;
			public uint ClientIP;
			public ushort DiscardPort;
			public ushort TestPort;
			public uint TestIP;
			public uint TrID;
			
			public static ProbePacket FromByteArray (byte[] data)
			{
				ProbePacket pkt = new ProbePacket ();
				
				pkt.Version = data[0];
				pkt.ServiceCode = data[1];
				pkt.ClientPort = BitUtility.ToUInt16 (data, 2, false);
				pkt.ClientIP = BitUtility.ToUInt32 (data, 4, false);
				pkt.DiscardPort = BitUtility.ToUInt16 (data, 8, false);
				pkt.TestPort = BitUtility.ToUInt16 (data, 10, false);
				pkt.TestIP = BitUtility.ToUInt32 (data, 12, false);
				pkt.TrID = BitUtility.ToUInt32 (data, 16, false);
				
				return pkt;
			}
			
			public byte[] ToByteArray ()
			{
				byte[] data = new byte[20];
				
				data[0] = Version;
				data[1] = ServiceCode;
				Array.Copy (BitUtility.FromUInt16 (ClientPort, false), 0, data, 2, 2);
				Array.Copy (BitUtility.FromUInt32 (ClientIP, false), 0, data, 4, 4);
				Array.Copy (BitUtility.FromUInt16 (DiscardPort, false), 0, data, 8, 2);
				Array.Copy (BitUtility.FromUInt16 (TestPort, false), 0, data, 10, 2);
				Array.Copy (BitUtility.FromUInt32 (TestIP, false), 0, data, 12, 4);
				Array.Copy (BitUtility.FromUInt32 (TrID, false), 0, data, 16, 4);
				
				return data;
			}
			
			public void Decrypt (ushort port, uint ip)
			{
				ClientPort ^= port;
				DiscardPort ^= port;
				TestPort ^= port;
				
				ClientIP ^= ip;
				TestIP ^= ip;
			}
		}
		
		public MsnEchoServer ()
		{
			IPHostEntry entry = Dns.GetHostEntry ("echo.edge.messenger.live.com");
			
			if (entry.AddressList.Length == 0)
			{
				Log.Warn ("Unable to resolve echo server domain");
				return;
			}
			
			_address = entry.AddressList[0];
			
			Log.Debug ("Using echo server {0}", _address);
			
			_connection = new DirectUDPConnection (false, _address, 7001);
			_connection.Connected += ConnectionConnected;
			_connection.Disconnected += ConnectionDisconnected;
			_connection.DataReceived += ConnectionDataReceived;
		}
		
		void ConnectionConnected (object sender, EventArgs args)
		{
			Log.Debug ("Connected to echo server");
			
			ProbePacket pkt = new ProbePacket ();
			pkt.Version = 2;
			pkt.ServiceCode = 1;
			pkt.ClientPort = 0x3141;
			pkt.ClientIP = 0x31413141;
			pkt.TrID = 1;
			
			byte[] data = pkt.ToByteArray ();
			
			Log.Debug (">> ECHO {0}", BaseUtility.BytesToString (data));
			_connection.Send (data);
		}
		
		void ConnectionDisconnected (object sender, EventArgs args)
		{
			Log.Debug ("Disconnected from echo server");
			
			_connection.Connected -= ConnectionConnected;
			_connection.Disconnected -= ConnectionDisconnected;
			_connection.DataReceived -= ConnectionDataReceived;
			
			_connection = null;
		}
		
		void ConnectionDataReceived (object sender, ByteArrayEventArgs args)
		{
			int origLen = _buffer.Length;
			Array.Resize (ref _buffer, origLen + args.Data.Length);
			Array.Copy (args.Data, 0, _buffer, origLen, args.Data.Length);
			
			Log.Debug ("Buffer length {0}", _buffer.Length);
			
			if (_buffer.Length >= 20)
			{
				Log.Debug ("<< ECHO {0}", BaseUtility.BytesToString (_buffer));
				
				ProbePacket pkt = ProbePacket.FromByteArray (_buffer);
				pkt.Decrypt (0x3141, 0x31413141);

				IPAddress ip = new IPAddress (pkt.ClientIP);
				
				Log.Debug ("Echo server reports external IP {0} ({1})", ip, pkt.ClientIP);
				
				NetworkUtility.IPAddress = ip;
				
				_connection.Disconnect ();
			}
		}
		
		public static void FindIP ()
		{
			if (NetworkUtility.IPAddress == null)
				new MsnEchoServer ();
		}
	}
}
